#include "schema_operate.h"
#include "url.h"
#include "keyword.h"
#include "assert.h"

#include "log.h"

#define DAY_SECONDS 			(60*60*24L)
#define OVERDUE_SECONDS  		(60*60*60*24L)	
#define SCHEMA_TABLES_COUNT 	512
#define HASH_MASK 				SCHEMA_TABLES_COUNT

static int get_hash(const char* str, int len)
{ 
    unsigned int hash = 2166136261UL;
    for(int i=0;i<len;i++)
        hash = (hash ^ *(str+i)) * 16777619;
    hash += hash << 13;
    hash ^= hash >> 7;
    hash += hash << 3;
    hash ^= hash >> 17;
    hash += hash << 5;
    return hash % HASH_MASK;
}

int find_table_name(const char* p, int len, 
                 const char* pre_table_name, char* table_name)
{
	assert( p != 0 && pre_table_name != 0 && 
		 	table_name != 0 && len > 0);
		 	
	char name[] = "%s_%03d";
	sprintf(table_name, name,pre_table_name, get_hash(p, len));
	return 0;	 	
}



int insert_to_schema(MYSQL* mysql, const char* pre_table_name, url_t* url)
{
	assert(mysql != NULL && pre_table_name != NULL && url != NULL);
	
	char* szurl =url->szurl;
	char table_name[16] = {};
	find_table_name(szurl, strlen(szurl), pre_table_name, table_name);
	
	char url_md5[MD5_BUFF_LEN] = {0};
	g_md5.generate((unsigned char*)szurl, strlen(szurl),
				   url_md5, sizeof(url_md5));	
				   
	time_t check_time = time(NULL);			   
	const char* insert = "insert into %s(MD5, URL, CHECKDATE) \
						  values('%s','%s', FROM_UNIXTIME(%d))";
	
						  
	char sql[1024] = {0};
	sprintf(sql, insert, table_name, url_md5, szurl, (int)check_time);	
	if( mysql_real_query(mysql, sql, strlen(sql)))
	{
		//if this url have exist, check time
		memset(sql, 0, sizeof(sql));
		const char* query = "select UNIX_TIMESTAMP(CHECKDATE) from %s where MD5='%s'";
		sprintf(sql, query,table_name, url_md5);
		if( mysql_real_query(mysql, sql, strlen(sql)) )
		{
			printf("query sql %d:%s\n",mysql_errno(mysql),mysql_error(mysql));
			return -1;
		}
		MYSQL_RES* res;
		if(( res = mysql_store_result(mysql)) == NULL)
		{
			printf("store_result sql %d:%s\n",mysql_errno(mysql),mysql_error(mysql));
			return -1;
		}
		MYSQL_ROW row = mysql_fetch_row(res);
		if( row == 0)
		{
			mysql_free_result(res);
			return 0;
		}
		time_t old_check_time = (time_t)atol(row[0]);
		if(abs( check_time - old_check_time ) > OVERDUE_SECONDS)
		{
			//update
			memset(sql, 0, sizeof(sql));
			const char* update = "update %s set	CHECKDATE=FROM_UNIXTIME(%d) \
		     				  where MD5 = '%s'";
			sprintf(sql, update, table_name,(int)check_time, url_md5, url);	
		
			if( mysql_real_query(mysql, sql, strlen(sql)) )
			{
				printf("update sql %d:%s\n",mysql_errno(mysql),mysql_error(mysql));
				return -1;
			}
		}
		else
		{
			//ignore this page
			printf("no need to update %s\n", url->szurl);
			return -1;
		}
	}				   
	return 0;
}


/////////////////////////////////////////////////////////////
int insert_to_url(MYSQL* mysql, url_t* url)
{
	if( mysql == 0 || url == 0)
		return -1;
	
	return insert_to_schema(mysql, "URL", url);
}

int	insert_to_pending(MYSQL* mysql, url_t* url)
{	
	if( mysql == 0 || url == 0)
		return -1;
	
	return insert_to_schema(mysql, "PENDING", url);
}
int	insert_to_invalid(MYSQL* mysql, url_t* url)
{
	if( mysql == 0 || url == 0)
		return -1;
	
	return insert_to_schema(mysql, "INVALID", url);
}
int	insert_to_dict(MYSQL* mysql, keyword_t* keyword)
{
	if( mysql == 0 || keyword == 0)
		return -1;
	
    
	char dict_table_name[16] = {0};
	char sql[1024] = {0};	
	const char* query = "select * from %s d where d.word = '%s'";
	const char* insert = "insert into %s( WORD, COUNT ) values('%s',%d)";
	const char* update = "update %s set	COUNT=%d where WORD = '%s'";
	const char* word = 0;
	int count = 0;
	map<string, int>::iterator it = keyword->keywords.begin();
	for(; it != keyword->keywords.end(); it++)
	{
		word = (*it).first.c_str();
		count = (*it).second;
		if( word == 0 || count == 0)
			continue;
		
		memset(dict_table_name,0,sizeof(dict_table_name));
		find_table_name(word, strlen(word), "DICT", dict_table_name);
		
		memset(sql, 0,sizeof(sql));
		sprintf(sql, query,dict_table_name, word);
		if( mysql_real_query(mysql, sql, strlen(sql)) )
		{
			printf("query sql %d:%s\n",mysql_errno(mysql),mysql_error(mysql));
			return -1;
		}
		MYSQL_RES* res;
		if(( res = mysql_store_result(mysql)) == NULL)
		{
			printf("store_result sql %d:%s\n",mysql_errno(mysql),mysql_error(mysql));
			return -1;
		}
		MYSQL_ROW row = mysql_fetch_row(res);
		if( row == 0)
		{
			mysql_free_result(res);
			//insert
			
			sprintf(sql, insert,dict_table_name, word, count);
			if( mysql_real_query(mysql, sql, strlen(sql)) )
			{
				printf("insert sql %d:%s\n",mysql_errno(mysql),mysql_error(mysql));
				return -1;
			}
		}
		else
		{
			//update
			int new_count = atol(row[1])+count; //count++;
			mysql_free_result(res);
			sprintf(sql, update, dict_table_name,new_count, word);
			if( mysql_real_query(mysql, sql, strlen(sql)) )
			{
				printf("update sql %d:%s\n",mysql_errno(mysql),mysql_error(mysql));
				return -1;
			}
		}
	}
	return 0;
}
int insert_to_keyword_impl(MYSQL* mysql, const char* keyword_table_name,
			const char* keyword, int count,
			const char* url_table_name, const char* url_md5, const char* url,
			time_t* check_time)
{
	if( mysql == 0 || keyword_table_name == 0 ||
		keyword == 0 || url_table_name == 0 || url_md5 ==  0)
		return -1;
		
	const char* query = "select t.id, t.next from %s t where t.KEYWORD='%s' and \
			t.WEIGHT = %d	";
	char sql[1024] ={0};
	sprintf(sql, query, keyword_table_name, keyword, count);
	if( mysql_real_query(mysql, sql, strlen(sql)) )
	{
		printf("query sql %d:%s\n",mysql_errno(mysql),mysql_error(mysql));
		return -1;
	}
	MYSQL_RES* res;
	if(( res = mysql_store_result(mysql)) == NULL)
	{
		printf("store_result sql %d:%s\n",mysql_errno(mysql),mysql_error(mysql));
		return -1;
	}
	MYSQL_ROW row = mysql_fetch_row(res);
	if( row == 0)
	{
		mysql_free_result(res);
		const char* insert = "insert into %s\
			   (KEYWORD,WEIGHT,NEXT,COUNT_0,URL_TB_0,URL_MD5_0,URL_0,CHECKDATE_0) \
		 values('%s',   %d,    1,   %d,     '%s',    '%s',     '%s', FROM_UNIXTIME(%d))";
		memset(sql, 0, sizeof(sql));
		sprintf(sql, insert,keyword_table_name, keyword, \
			count,count,url_table_name, url_md5, url,(int)(*check_time));
		printf("insert_to_keyword_impl:row == 0:%s\n",sql);
		if( mysql_real_query(mysql, sql, strlen(sql)) )
		{
			printf("insert sql %d:%s\n",mysql_errno(mysql),mysql_error(mysql));
			return -1;
		}
		logger("insert_to_keyword_impl:insert:%s\n",sql);
	}
	else
	{
		long id = atol(row[0]);
		int next = atoi(row[1]);
		
		mysql_free_result(res);
		if( next < 0)
		{
			next = 0;
		}
		
		if( next >= 0 && next < 9)
		{
			const char* update = "update %s set NEXT=%d, COUNT_%d = %d,\
					URL_TB_%d='%s',URL_MD5_%d='%s',URL_%d='%s',\
					CHECKDATE_%d=FROM_UNIXTIME(%d) where \
					id=%d ";
			memset(sql, 0, sizeof(sql));
			sprintf(sql, update,keyword_table_name, next+1,next,count,\
				    next, url_table_name, next,url_md5,next,url,\
				    next,(int)(*check_time),\
				    id);
			printf("insert_to_keyword_impl:next >= 0 && next < 9:%s\n",sql);
			if( mysql_real_query(mysql, sql, strlen(sql)) )
			{
				printf("insert sql %d:%s\n",mysql_errno(mysql),mysql_error(mysql));
				return -1;
			}
			logger("insert_to_keyword_impl:update:%s\n",sql);
		}
		else if( next >= 9)
		{
			const char* insert_new  = "insert into %s\
					  (KEYWORD,WEIGHT,NEXT,COUNT_0,URL_TB_0,URL_MD5_0,URL_0,CHECKDATE_0) \
		 		values('%s',   %d,    1,   %d,     '%s',    '%s',     '%s', FROM_UNIXTIME(%d))";
				memset(sql, 0, sizeof(sql));
			sprintf(sql, insert_new,keyword_table_name, \
					keyword, count,count,url_table_name, url_md5, url,(int)(*check_time));
			logger("insert_to_keyword_impl:next >= 9:%s\n",sql);
			if( mysql_real_query(mysql, sql, strlen(sql)) )
			{
				printf("insert sql %d:%s\n",mysql_errno(mysql),mysql_error(mysql));
				return -1;
			}
			logger("insert_to_keyword_impl:insert:%s\n",sql);
		}
		
		
	}
	return 0;
}		
int insert_to_keyword(MYSQL* mysql, keyword_t* keyword)
{
	if(mysql == NULL || keyword == NULL)
		return -1;
		
	char url_table_name[16] = 	{0};
	char* szurl = keyword->url->szurl;	
	find_table_name(szurl, strlen(szurl), "URL", url_table_name);
                 
    char url_md5[MD5_BUFF_LEN] = {0};
	g_md5.generate((unsigned char*)szurl, strlen(szurl),
				   url_md5, sizeof(url_md5));	
				   
	time_t check_time = time(NULL);			   
	             
	map<string, int>::iterator it = keyword->keywords.begin();
	char keyword_table_name[64] = {0};
	const char* szkeyword;
	int count;
	for(; it != keyword->keywords.end(); it++)
	{
		szkeyword = (*it).first.c_str();
		count = (*it).second;
		if( szkeyword == 0 || count == 0)
			continue;
		
		
		memset(keyword_table_name, 	0, sizeof(keyword_table_name));
		if(find_table_name(szkeyword, strlen(szkeyword), "KEYWORD", keyword_table_name) !=0)
			continue;
		if(insert_to_keyword_impl(mysql, keyword_table_name, szkeyword, 
				count, url_table_name, url_md5, szurl,&check_time) != 0)
			continue;
	}
	
	return 0;
}

int select_from_schema(MYSQL* mysql, const char* pre_table_name, int limit,list<url_t*>& urls)
{
	assert(mysql != NULL && pre_table_name != NULL);
	
	//only use in one thread, not in multi-threads
	static int table_choice = 0;
	table_choice++;
	if(table_choice >= SCHEMA_TABLES_COUNT)
		table_choice = 0;
		
	char table_name[16] = {};
	char name[] = "%s_%03d";
	sprintf(table_name, name,pre_table_name, table_choice);
	
	
	const char* query = "select t.URL from %s t order by t.CHECKDATE limit %d ";
	char sql[1024] = {0};
	sprintf(sql, query, table_name, limit);	
		
	if( mysql_real_query(mysql, sql, strlen(sql)))	
	{
		printf("query sql %d:%s\n",mysql_errno(mysql),mysql_error(mysql));
		return -1;
	}
	MYSQL_RES* res;
	if(( res = mysql_store_result(mysql)) == NULL)
	{
		printf("store_result sql %d:%s\n",mysql_errno(mysql),mysql_error(mysql));
		return -1;
	}
//	int fields = mysql_num_fields(res);
	MYSQL_ROW row;
	while( (row = mysql_fetch_row(res)) )
	{
		
		const char* szurl = row[0];
	//	if( url == NULL);
	//		break;
		
		
		url_t* url = create_url(szurl, strlen(szurl));
		if(url == 0 || strlen(url->szurl)==0)
			continue;
		
		urls.push_back(url);
			
	}	
	mysql_free_result(res);
	return 0;
}

int select_from_pending(MYSQL* mysql,unsigned int limit,list<url_t*>& urls)
{
	if(mysql == NULL )
		return -1;
	return select_from_schema(mysql, "PENDING",limit, urls);
}

int exist_in_schema(MYSQL* mysql,const char* pre_table_name, url_t* url)
{
	assert(mysql != NULL && pre_table_name != NULL && url != NULL);
		
	char* szurl = url->szurl;	
	char url_md5[MD5_BUFF_LEN] = {0};
	g_md5.generate((unsigned char*)szurl, strlen(szurl),
				   url_md5, sizeof(url_md5));
				   
	char table_name[16] = {};
	find_table_name(szurl, strlen(szurl), pre_table_name, table_name);
				   
	const char* query = "select count(*) from %s where MD5='%s' ";
	char sql[1024] = {0};
	sprintf(sql, query, table_name, url_md5);	
		
	if( mysql_real_query(mysql, sql, strlen(sql)))
	{
		printf("query sql %d:%s\n",mysql_errno(mysql),mysql_error(mysql));
		return -1;
	}
	MYSQL_RES* res;
	if(( res = mysql_store_result(mysql)) == NULL)
	{
		printf("store_result sql %d:%s\n",mysql_errno(mysql),mysql_error(mysql));
		return -1;
	}
	MYSQL_ROW row = mysql_fetch_row(res);
	int count = atoi(row[0]);
	mysql_free_result(res);
	return count;
}

int exist_in_pending(MYSQL* mysql, url_t* url)
{
	if( mysql == NULL || url == NULL)
		return -1;
	return exist_in_schema(mysql, "PENDING", url);
}
int exist_in_invalid(MYSQL* mysql, url_t* url)
{
	if( mysql == NULL || url == NULL)
		return -1;
	return exist_in_schema(mysql, "INVALID", url);
}
int exist_in_url(MYSQL* mysql, url_t* url)
{
	if( mysql == NULL || url == NULL)
		return -1;
	return exist_in_schema(mysql, "URL", url);
}



int delete_from_schema(MYSQL* mysql, const char* pre_table_name, url_t* url)
{
	assert( mysql != NULL && pre_table_name != 0 && url != 0 );
		
	char* szurl = url->szurl;	
	char url_md5[MD5_BUFF_LEN] = {0};
	g_md5.generate((unsigned char*)szurl, strlen(szurl),
				   url_md5, sizeof(url_md5));	
	char table_name[16] = {};
	find_table_name(szurl, strlen(szurl), pre_table_name, table_name);
				   
	const char* del = "delete from %s  where MD5='%s'";
	char sql[1024] = {0};
	sprintf(sql, del, table_name, url_md5);	
		
	if( mysql_real_query(mysql, sql, strlen(sql)))	
	{
		printf("delete sql%s::%d:%s\n",sql,mysql_errno(mysql),mysql_error(mysql));
		return -1;
	}
	return 0;
}
int	delete_from_pending(MYSQL* mysql, url_t* url)
{
	if( mysql == NULL || url == NULL)
		return -1;
	return delete_from_schema(mysql, "PENDING", url);
}
int	delete_from_invalid(MYSQL* mysql, url_t* url)
{
	if( mysql == NULL || url == NULL)
		return -1;
	return delete_from_schema(mysql, "INVALID", url);
}

